/*
 * Based on code submited by Hamed_ji  (http://www.codeproject.com/cs/library/Do_Anything_With_ID3.asp)
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace svs.Mobile.BeyondMedia.FeedCore.ID3v2
{
	internal class FileStreamEx
	{
		private readonly FileStream _FileStream;
		private MemoryStream _MemoryStream;

		public FileStreamEx(string path, FileMode mode)
		{
			_FileStream = new FileStream(path, mode);
		}

		private Stream CurrentStream
		{
			get
			{
				if (_FileStream.CanRead)
					return _FileStream;
				else
					return _MemoryStream;
			}
		}

		public void SwitchToResyncronizedStream(int Size)
		{
			if (_FileStream != null && _FileStream.CanRead)
			{
				_MemoryStream = GetReSyncronizedStream(_FileStream.Position, Size);
				_FileStream.Close();
			}
		}

		private MemoryStream GetReSyncronizedStream(long Start, int size)
		{
			byte[] bytes = new byte[4096];
			int bytesToRead = size;
			List<Byte> vector = new List<byte>(size);

			_FileStream.Position = Start;

			do
			{
				int bytes_read = _FileStream.Read(bytes, 0, Math.Min(bytes.Length, bytesToRead));
				bytesToRead -= bytes_read;
				vector.AddRange(bytes);
			} while (bytesToRead > 0);

			//trim the vector to size
			if(vector.Count > size)
				 vector.RemoveRange(size,vector.Count-size);

			for (int i = vector.Count - 2; i >= 0; i--)
				if (vector[i] == 0xFF && vector[i + 1] == 0)
					vector.RemoveAt(i + 1);

			return new MemoryStream(vector.ToArray());
		}


		/// <summary>
		/// Read string from current FileStream
		/// </summary>
		/// <param name="MaxLength">Maximum length that can read from stream</param>
		/// <param name="TEncoding">TextEcoding to read from Stream</param>
		/// <param name="DetectEncoding">Can method recognize encoding of text from Encoding inicators</param>
		/// <returns>string readed from current FileStream</returns>
		public string ReadText(int MaxLength, ID3Info.TextEncodings TEncoding, ref int ReadedLength, bool DetectEncoding)
		{
			if (MaxLength <= 0)
				return "";
			long Pos = CurrentStream.Position;

			MemoryStream MStream = new MemoryStream();
			if (DetectEncoding && MaxLength >= 3)
			{
				byte[] Buffer = new byte[3];
				CurrentStream.Read(Buffer, 0, Buffer.Length);
				if (Buffer[0] == 0xFF && Buffer[1] == 0xFE)
				{
					// FF FE
					TEncoding = ID3Info.TextEncodings.UTF_16; // UTF-16 (LE)
					CurrentStream.Position--;
					MaxLength -= 2;
				}
				else if (Buffer[0] == 0xFE && Buffer[1] == 0xFF)
				{
					// FE FF
					TEncoding = ID3Info.TextEncodings.UTF_16BE;
					CurrentStream.Position--;
					MaxLength -= 2;
				}
				else if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)
				{
					// EF BB BF
					TEncoding = ID3Info.TextEncodings.UTF8;
					MaxLength -= 3;
				}
				else
					CurrentStream.Position -= 3;
			}
			bool Is2ByteSeprator = (TEncoding == ID3Info.TextEncodings.UTF_16 || TEncoding == ID3Info.TextEncodings.UTF_16BE);

			byte[] currSymbol = new byte[2];

			while (MaxLength > 0)
			{
				currSymbol[0] = ReadByte(); // Read First/Next byte from stream
				MaxLength--;

				if (Is2ByteSeprator)
				{
					currSymbol[1] = ReadByte();
					MaxLength--;
				}

				bool isEndOfString = currSymbol[0] == 0 && ((Is2ByteSeprator && currSymbol[1] == 0) || !Is2ByteSeprator);

				if (isEndOfString)
					break;
				else
				{
					MStream.Write(currSymbol, 0, Is2ByteSeprator ? 2 : 1);
				}
			}

			if (MaxLength < 0)
				CurrentStream.Position += MaxLength;

			ReadedLength -= Convert.ToInt32(CurrentStream.Position - Pos);

			byte[] data = MStream.ToArray();
			return GetEncoding(TEncoding).GetString(data, 0, data.Length);
		}

		public string ReadText(int MaxLength, ID3Info.TextEncodings TEncoding)
		{
			int i = 0;
			return ReadText(MaxLength, TEncoding, ref i, true);
		}

		public string ReadText(int MaxLength, ID3Info.TextEncodings TEncoding, bool DetectEncoding)
		{
			int i = 0;
			return ReadText(MaxLength, TEncoding, ref i, DetectEncoding);
		}

		/// <summary>
		/// Read a byte from current FileStream
		/// </summary>
		/// <returns>Readed byte</returns>
		public byte ReadByte()
		{
			byte[] RByte = new byte[1];

			// Use read method of FileStream instead of ReadByte
			// Becuase ReadByte return a SIGNED byte as integer
			// But what we want here is unsigned byte
			CurrentStream.Read(RByte, 0, 1);

			return RByte[0];
		}

		/// <summary>
		/// Read some bytes from FileStream and return it as unsigned integer
		/// </summary>
		/// <param name="Length">length of number in bytes</param>
		/// <returns>uint represent number readed from stream</returns>
		public uint ReadUInt(int Length)
		{
			if (Length > 4 || Length < 1)
				throw (new ArgumentOutOfRangeException("ReadUInt method can read 1-4 byte(s)"));

			byte[] Buf = new byte[Length];
			byte[] RBuf = new byte[4];
			CurrentStream.Read(Buf, 0, Length);
			Buf.CopyTo(RBuf, 4 - Buf.Length);
			Array.Reverse(RBuf);
			return BitConverter.ToUInt32(RBuf, 0);
		}

		/// <summary>
		/// Read data from specific FileStream and return it as MemoryStream
		/// </summary>
		/// <param name="Length">Length that must read</param>
		/// <returns>MemoryStream readed from FileStream</returns>
		public MemoryStream ReadData(int Length)
		{
			MemoryStream ms;
			byte[] Buf = new byte[Length];
			CurrentStream.Read(Buf, 0, Length);
			ms = new MemoryStream();
			ms.Write(Buf, 0, Length);

			return ms;
		}

		/// <summary>
		/// Indicate if file contain ID3v2 information
		/// </summary>
		/// <returns>true if contain otherwise false</returns>
		public bool HaveID3v2()
		{
			/* if the first three characters in begining of a file
             * be "ID3". that mpeg file contain ID3v2 information
             */
			string Iden = ReadText(3, ID3Info.TextEncodings.Ascii);
			if (Iden == "ID3")
				return true;
			else
				return false;
		}

		/// <summary>
		/// Indicate if current File have ID3v1
		/// </summary>
		public bool HaveID3v1()
		{
			CurrentStream.Seek(-128, SeekOrigin.End);
			string Tag = ReadText(3, ID3Info.TextEncodings.Ascii);
			if (Tag == "TAG")
				return true;
			else
				return false;
		}

		/// <summary>
		/// Read ID3 version from current file
		/// </summary>
		/// <returns>Version contain ID3v2 version</returns>
		public Version ReadVersion()
		{
			return new Version("2." + ReadByte().ToString() + "." +
			                   ReadByte().ToString());
		}

		/// <summary>
		/// Read ID3 Size
		/// </summary>
		/// <returns>ID3 Length size</returns>
		public int ReadSize()
		{
			/* ID3 Size is like:
             * 0XXXXXXXb 0XXXXXXXb 0XXXXXXXb 0XXXXXXXb (b means binary)
             * the zero bytes must ignore, so we have 28 bits number = 0x1000 0000 (maximum)
             * it's equal to 256MB
             */
			int RInt;
			RInt = ReadByte()*0x200000;
			RInt += ReadByte()*0x4000;
			RInt += ReadByte()*0x80;
			RInt += ReadByte();
			return RInt;
		}

		public static Encoding GetEncoding(ID3Info.TextEncodings TEncoding)
		{
			switch (TEncoding)
			{
				case ID3Info.TextEncodings.UTF_16:
					return Encoding.Unicode;
				case ID3Info.TextEncodings.UTF_16BE:
					return Encoding.GetEncoding("UTF-16BE");
				case ID3Info.TextEncodings.UTF8:
					return Encoding.UTF8;
				default:
					return Encoding.Default;
			}
		}

		public void Seek(long i, SeekOrigin current)
		{
			CurrentStream.Seek(i, current);
		}

		public void Close()
		{
			if(_FileStream != null)
				_FileStream.Close();

			if(_MemoryStream != null)
				_MemoryStream.Close();
		}
	}
}